package com.syf.service.impl;

import com.syf.constant.LoginConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 *
 * 登录的时候都是通过用户名查找数据是否存在，如果有两个相同的用户名呢
 * 查询到两条记录，密码也恰好一样呢
 *
 *
 * 1.密码登录:
 *      用户名,密码 -> 登录 -> token(user_name = "id")
 * 2.REFRESH_TYPE刷新token自动登录:
 *      user_name = "id",需要进行纠正: id -> username/mobile手机号码
 *
 * 3.该方法是如何被调用的:
 *      用户请求： http://localhost:9999/oauth/token?grant_type=password&username=admin&password=123456&login_type=member_type
 *      授权服务器(AuthorizationServerConfig类中) 会调用 UserDetailsService接口的loadUserByUsername()方法 获取及验证用户的密码账户
 *      去加载我们的私钥 生成token,存储token
 *
 */
@Service
public class UserServiceDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 登录实现:【Postman工具】
     *      http://localhost:9999/oauth/token?grant_type=password&username=admin&password=123456&login_type=admin_type
     *
     *      //直接创建了一个用户
     *      return new User("user", "{noop}123456", AuthorityUtils.createAuthorityList("ROLE_USER"));
     *
     * @param username 用户名称
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取login_type属性: 区分是 后台人员(admin_type)/用户(会员 member_type) 登录
        String loginType = requestAttributes.getRequest().getParameter("login_type");
        if (StringUtils.isEmpty(loginType)) {
            throw new AuthenticationServiceException("登录类型不能为null");
        }

        UserDetails userDetails = null; //该接口实现: 存储用户的信息
        try {
            //若"grant_type"=REFRESH_TYPE(刷新token类型)，纠正用户的名称:
            String grantType = requestAttributes.getRequest().getParameter("grant_type"); // refresh_token 进行纠正
            if (LoginConstant.REFRESH_TYPE.equals(grantType.toUpperCase())) {   //toUpperCase():将字符串小写字符转换为大写
                username = adjustUsername(username, loginType); //3.纠正用户的名称(用户名，登录类型)
            }

            switch (loginType) {
                case LoginConstant.ADMIN_TYPE:
                    userDetails = loadSysUserByUsername(username);  //1.后台人员的登录【根据用户名查询数据是否存在】
                    break;
                case LoginConstant.MEMBER_TYPE:
                    userDetails = loadMemberUserByUsername(username);  //2.会员的登录
                    break;
                default:
                    throw new AuthenticationServiceException("暂不支持的登录方式:" + loginType);
            }
        } catch (IncorrectResultSizeDataAccessException e) { //登录验证用户名/密码错误处理【密码内部验证】
            throw new UsernameNotFoundException("错错错错错错错错");  //提升用户体验度，固定输出: 用户名或密码错误
        }

        return userDetails; //登录返回
    }

    /**
     * 3.纠正用户的名称：
     *      【根据token中的用户id 查询各自的 username用户名】
     *
     * @param username  用户的id
     * @param loginType admin_type  member_type
     * @return
     */
    private String adjustUsername(String username, String loginType) { //此时，username为用户id，非用户名
        if (LoginConstant.ADMIN_TYPE.equals(loginType)) {
            // 管理员的纠正方式【根据用户id查询 username用户名】
            return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_USER_WITH_ID,String.class ,username);
        }
        if (LoginConstant.MEMBER_TYPE.equals(loginType)) {
            // 会员的纠正方式【根据用户id查询 mobile手机号码】
            return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_USER_WITH_ID,String.class ,username);
        }
        return username;
    }

    /**
     * 1.后台人员的登录【根据用户名查询结果是否为空 -> 用户id(查询相应的权限)】
     *
     * @param username
     * @return
     */
    private UserDetails loadSysUserByUsername(String username) {
        // 1 使用用户名查询用户
        return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_SQL, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                if (rs.wasNull()) { //rs: 数据库查询出来的结果集
                    throw new UsernameNotFoundException("用户名" + username + "不存在");
                }
                long id = rs.getLong("id"); // 用户的id
                String password = rs.getString("password"); // 用户的密码
                int status = rs.getInt("status");

                return new User(   // 3 封装成一个UserDetails对象，返回
                        String.valueOf(id), //使用用户的 id 代替用户的名称，这样会使 得后面的很多情况得以处理
                        password,
                        status == 1,
                        true,
                        true,
                        true,
                        getSysUserPermissions(id) //查询这个用户对应的权限(通过用户的id 查询用户的权限)
                );
            }
        }, username);//(执行的SQL，返回值类型，参数)
    }//InvalidGrantException


    /**
     * 查询这个用户对应的权限(通过用户的id -> 用户的角色Code -> 相应权限)
     */
    private Collection<? extends GrantedAuthority> getSysUserPermissions(long id) {
        // 1 根据id查询该用户的 角色Code
        String roleCode = jdbcTemplate.queryForObject(LoginConstant.QUERY_ROLE_CODE_SQL, String.class, id);

        List<String> permissions = null; // 权限的名称
        if (LoginConstant.ADMIN_ROLE_CODE.equals(roleCode)) { // 超级用户("ROLE_ADMIN"=roleCode: 当用户为超级管理员时，他拥有所有的权限数据)
            permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_ALL_PERMISSIONS, String.class); //查询所有的权限名称
        } else { // 2 普通用户: 用户id -> 用户角色->权限数据
            permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_PERMISSION_SQL, String.class, id);
        }


        if (permissions == null || permissions.isEmpty()) {
            return Collections.emptySet(); //返回null
        }
        return permissions.stream() //组装我们的权限数据,生成Stream数据源
                .distinct() // 去重
                .map(perm -> new SimpleGrantedAuthority(perm))  //流Stream 转换成 map数据结构,再将map中的数据转化成 SimpleGrantedAuthority【perm 只是一个临时表示map容器数据的变量，不固定】
                .collect(Collectors.toSet()); //最后收集到Collection容器中
    }

    /**
     * 2.会员的登录【普通用户没权限: "ROLE_USER" 默认权限】
     *
     * @param username
     * @return
     */
    private UserDetails loadMemberUserByUsername(String username) { //参数username: 可能是 手机号/邮箱登录/用户名
        return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_SQL, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                if (rs.wasNull()) {
                    throw new UsernameNotFoundException("用户：" + username + "不存在");
                }
                long id = rs.getLong("id"); // 会员的id
                String password = rs.getString("password");// 会员的登录密码
                int status = rs.getInt("status"); // 会员的状态

                return new User(
                        String.valueOf(id), //使用用户的 id 代替用户的名称，这样会使 得后面的很多情况得以处理
                        password,
                        status == 1,
                        true,
                        true,
                        true,
                        Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")) //默认权限
                );
            }
        }, username, username,username);//手机号/邮箱登录/用户名 ->登录
    }


}


